# -*- coding: utf-8 -*-

import os
import re

from common.pathmgr import PathConfig
from common.filemgr import yield_tree_files

if os.name == 'nt':
    import win32con
    import win32file
    import pywintypes

    FolderExistsError = (WindowsError, OSError)
    FolderExistsCode = (183, 17)
elif os.name == 'posix':
    import fcntl

    FolderExistsError = (OSError,)
    FolderExistsCode = (17,)
else:
    raise RuntimeError("FileLocker only defined for nt and posix platforms")


class HasBeenLockedError(Exception):
    pass


class FileLocker(object):
    def __init__(self, lockerName, expiredSeconds=0, lockerFilePath=''):
        if lockerFilePath != '':
            lockerFolder = os.path.dirname(lockerFilePath)
            self.__lockerFilePath = lockerFilePath
        else:
            lockerName = lockerName.lower().strip()
            lockerName = re.sub('\s+', '_', lockerName)
            lockerFolder = PathConfig.FILE_LOCKER
            self.__lockerFilePath = os.path.join(lockerFolder, lockerName)
        self.__expiredSeconds = expiredSeconds
        self.__file = None
        self.__isLocked = False
        if not os.path.exists(lockerFolder):
            try:
                os.makedirs(lockerFolder)
            except FolderExistsError as e:
                if not e.args[0] in FolderExistsCode:
                    raise

    def lock(self, breakOnFailed=False):
        self.__file = open(self.__lockerFilePath, "w")
        self.__lock(breakOnFailed)
        self.__isLocked = True
        if self.__expiredSeconds > 0:
            import threading
            t = threading.Thread(target=self.__daemonMethod)
            t.setDaemon(True)
            t.start()

    def unlock(self):
        if not self.__isLocked: return
        self.__isLocked = False
        if not self.__file: return
        self.__unlock()
        self.__file.close()
        self.__file = None

    def __del__(self):
        self.unlock()

    def __daemonMethod(self):
        waitSeconds = 0
        import time
        while self.__isLocked and waitSeconds < self.__expiredSeconds:
            waitSeconds += 2
            time.sleep(2)
        if self.__isLocked:
            self.unlock()

    if os.name == 'nt':
        __overlapped = pywintypes.OVERLAPPED()

        def __lock(self, breakOnFailed):
            lockType = win32con.LOCKFILE_EXCLUSIVE_LOCK
            if breakOnFailed:
                lockType = lockType | win32con.LOCKFILE_FAIL_IMMEDIATELY
            hfile = win32file._get_osfhandle(self.__file.fileno())
            try:
                win32file.LockFileEx(hfile, lockType, 0, 0xf0, FileLocker.__overlapped)
            except pywintypes.error:
                self.__file.close()
                self.__file = None
                raise HasBeenLockedError("Locker has been acquired before")

        def __unlock(self):
            hfile = win32file._get_osfhandle(self.__file.fileno())
            win32file.UnlockFileEx(hfile, 0, 0xf0, FileLocker.__overlapped)

    elif os.name == 'posix':
        def __lock(self, breakOnFailed):
            lockType = fcntl.LOCK_EX
            if breakOnFailed:
                lockType = lockType | fcntl.LOCK_NB
            try:
                fcntl.flock(self.__file.fileno(), lockType)
            except IOError as e:
                self.__file.close()
                self.__file = None
                import errno
                if e.errno == errno.EACCES or e.errno == errno.EAGAIN:
                    raise HasBeenLockedError("Locker has been acquired before")
                raise

        def __unlock(self):
            import fcntl
            fcntl.flock(self.__file.fileno(), fcntl.LOCK_UN)


def clean_filelock_dir():
    for file_path in yield_tree_files(PathConfig.FILE_LOCKER):
        locker = FileLocker('', lockerFilePath=file_path)
        try:
            locker.lock(True)
            os.remove(file_path)
            print('remove %s' % file_path)
        except HasBeenLockedError:
            print('%s has locking' % file_path)
        except Exception:
            print('remove %s fail' % file_path)
        finally:
            locker.unlock()


if __name__ == "__main__":
    lock_name = os.path.basename(os.path.abspath(__file__))
    locker = FileLocker(lock_name)
    try:
        locker.lock(True)
        try:
            clean_filelock_dir()
        finally:
            locker.unlock()
    except HasBeenLockedError:
        print('%s is running. only one process need' % lock_name)
